By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 Semantic CSS.md

View raw Download
text/plain • 10.16 kiB
ASCII text

title: Let's write more semantic CSS topics: ["web", "css", "html"] DATE: 2024-05-18 ---

You probably wrote something like this at least once in your life:

<div class="card card--rounded card--primary">
   <div class="card__image-container">
       <img src="image.jpg" alt="A nice image" class="card__image">
       <span class="card__image-caption">A nice image</span>
   </div>
   <div class="card__content">
       <div class="card__header">
           <div class="card__title">Hello, world!</div>
       </div>
       <p class="card__text">
           Lorem ipsum dolor sit amet, consectetur adipiscing elit.
       </p>
   </div>
   <div class="card__footer">
       <button class="btn btn--primary btn--raised btn--accent card__button card__button--primary">Click me!</button>
       <button class="btn btn--secondary btn--raised btn--accent card__button card__button--secondary">Click me!</button>
   </div>
</div>

Or this:

<div class="max-w-sm rounded overflow-hidden shadow-lg">
   <div>
       <img class="w-full" src="image.jpg" alt="A nice image">
       <span class="text-gray-500 text-base">A nice image</span>
   </div>
   <div class="px-6 py-4">
       <div>
           <div class="font-bold text-xl mb-2">Hello, world!</div>
       </div>
       <p class="text-gray-700 text-base">
           Lorem ipsum dolor sit amet, consectetur adipiscing elit.
       </p>
   </div>
   <div class="px-6 py-4">
       <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Click me!</button>
       <button class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded">Click me!</button>
   </div>
</div>

The second one is an adapted example from the Tailwind CSS docs. The first one is a variant that uses BEM instead. Both of them have many problems.

HTML has got over 100 elements you could use to structure your content. These examples use only 5: div, span, p, img, and button. This is not a problem in itself for small components, but it can indicate one. Using div and span for everything means you're misusing HTML. This is wrong: don't overlook HTML. JS or CSS may be more interesting, but the document language of the WWW is HTML.

The first example uses classes in place of elements. This creates extra work for both the HTML and CSS author. The CSS still mirrors the HTML structure, and the HTML is much more verbose than it needs to be. The word "button" or "btn" appears 8 times for each button. Ideally, it should appear two times: once in the opening tag and once in the closing tag.

The second example intentionally has the same markup tree as the first one. However, the classes changed a lot. Tailwind uses classes instead of CSS rules. It leads to repetition. If you don't want to repeat, you use components. But what if you don't do components? Then use @apply in CSS. Yes, CSS. So you're basically writing CSS only with a different syntax and less flexibility.

A Simpler Way

Let's strip the classes and focus on the markup tree for now. The two examples are identical in this regard.

<div>
   <div>
       <img src="image.jpg" alt="A nice image">
       <span>A nice image</span>
   </div>
   <div>
       <div>
           <div>Hello, world!</div>
       </div>
       <p>
           Lorem ipsum dolor sit amet, consectetur adipiscing elit.
       </p>
   </div>
   <div>
       <button>Click me!</button>
       <button>Click me!</button>
   </div>
</div>

Now you see what I said? This tree is not semantic at all. Let's find the appropriate elements for each generic one.

<article>
   <figure>
       <img src="image.jpg" alt="A nice image">
       <figcaption>A nice image</figcaption>
   </figure>
   <section>
       <header>
           <h2>Hello, world!</h2>
       </header>
       <p>
           Lorem ipsum dolor sit amet, consectetur adipiscing elit.
       </p>
   </section>
   <menu>
       <button>Click me!</button>
       <button>Click me!</button>
   </menu>
</article>

In case you're not familiar with the new elements, the quick meaning is:

  • article - a self-contained piece of content that makes sense independently from the rest of the page

  • figure - a piece of content that is referenced from the main content, but can stand alone

  • figcaption - a caption for a figure's other content (optional)

  • section - a thematic grouping of content, typically with a heading

  • header - header for the document or a smaller part of it, can include context, navigation or information about the content

  • h2 - a second-level section heading (you probably knew this one already)

  • menu - a list of commands available to take on a specific part of the content

Please read the MDN articles I linked if you want to know more about these elements.

Depending on the other needs of your website or application, you will probably need to add a few classes. However, unlike the other examples, classes should be used as little as possible. Let's remember some things from the examples:

  • The article is supposed to be a card and styled as such.

  • The first button is the primary action, and the second one is the secondary action.

In this site, let's say not all articles are cards, but since this one is a card, we'll classify it as such. Let's also say that the secondary buttons are more common, this means we'll add a class to the primary button and style that later.

<article class="card">
   <figure>
       <img src="image.jpg" alt="A nice image">
       <figcaption>A nice image</figcaption>
   </figure>
   <section>
       <header>
           <h2>Hello, world!</h2>
       </header>
       <p>
           Lorem ipsum dolor sit amet, consectetur adipiscing elit.
       </p>
   </section>
   <menu>
       <button class="button-primary">Click me!</button>
       <button>Click me!</button>
   </menu>
</article>

Now, let's write a basic stylesheet for this. It won't look exactly like the second example for the sake of simplicity, but it could easily be made to look like that. We're going to use a CSS selector you've probably only seen in resets and to set the font on the html element, the tag selector. We're also going to use some new CSS smarts to make the styles more maintainable.

html, button, input, select, textarea {
   font-family: system-ui, sans-serif;
}
article.card {
   background-color: whitesmoke;
   border-radius: 12px;
   box-shadow: 0 0 4px #00000040;
   display: flex;
   flex-direction: column;
   gap: 1rem;
   overflow: hidden;
}
figure {
   display: flex;
   flex-direction: column;
   gap: 0.25rem;
}
figure > img {
   width: 100%;
   height: auto;
}
figcaption {
   font-style: italic;
   opacity: 0.875;
}
article.card > section {
   padding-left: 1rem;
   padding-right: 1rem;
}
article.card > menu, menu.buttonbox {
   display: flex;
   gap: 1rem;
   justify-content: flex-end;
}
button, .button,  /* provide alternative where it makes sense, since we may want to make something else look like a button */
input:is([type="button"], [type="submit"], [type="reset"]) {
   background-color: white;
   color: orange;
   border: 4px solid currentColor;
   padding: 0.5rem 1rem;
   display: inline-flex;
   align-items: center;
   gap: 0.5rem;
   border: none;
   border-radius: 4px; /* Border radii are a decoration so pixels are fine */
}
:is(button, .button, input:is([type="button"], [type="submit"], [type="reset"])).button-primary {
   background-color: orange;
   color: white;
}

Observations:

  • We provide alternatives for some tag selectors where it makes sense, in case we want to make something else look like a button. However, we don't force using both when it's already clear: <button> will produce a styled button, same as <a class="button">. <button class="button"> is redundant.

  • The > child selector is used to avoid leaking styles in more complex nested layouts.

  • We use the :is() pseudo-class to group selectors that have the same styles. This is a new feature in CSS and it saves us from writing an enormous amount of combinations.

Conclusion

Now, writing HTML is much easier: the CSS will adapt to what you intended to describe. The CSS is also much easier to maintain: the style can be changed easily without changing the HTML. The elements are always styled automatically, and you can copy-paste a snippet of plain HTML and have it magically match the rest of your site.

A more complete framework for this could add some layout container utilities. For example, a grid class that makes the element a grid container and uses --width and --gap custom properties to position the children. There could also be layout elements to use in place of divs like x-hbox and x-vbox that are flex containers. This would indicate the default style, and an additional class or ID would be used to make them responsive as well. Utility classes aren't bad, but they should be used for the things that can't cause repetition - which side a dialogue should emerge from, or whether to add padding in a generic row container.

Frameworks Using Semantic CSS

  • Pico CSS - does it very well, I should take some inspiration from it

  • Water.css - a very minimalistic CSS framework, primarily intended for publishing, but also includes interactive elements

  • MVP.css - a basic stylesheet for plain HTML made for any site to look acceptable

  • The roundabout also uses semantic CSS. Once the API is stabilised a little the CSS will be released as a framework.

  • You might not even need a framework.

                
                    
1
---
2
title: Let's write more semantic CSS
3
topics: ["web", "css", "html"]
4
DATE: 2024-05-18
5
---
6
7
You probably wrote something like this at least once in your life:
8
```html
9
<div class="card card--rounded card--primary">
10
<div class="card__image-container">
11
<img src="image.jpg" alt="A nice image" class="card__image">
12
<span class="card__image-caption">A nice image</span>
13
</div>
14
<div class="card__content">
15
<div class="card__header">
16
<div class="card__title">Hello, world!</div>
17
</div>
18
<p class="card__text">
19
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
20
</p>
21
</div>
22
<div class="card__footer">
23
<button class="btn btn--primary btn--raised btn--accent card__button card__button--primary">Click me!</button>
24
<button class="btn btn--secondary btn--raised btn--accent card__button card__button--secondary">Click me!</button>
25
</div>
26
</div>
27
```
28
Or this:
29
```html
30
<div class="max-w-sm rounded overflow-hidden shadow-lg">
31
<div>
32
<img class="w-full" src="image.jpg" alt="A nice image">
33
<span class="text-gray-500 text-base">A nice image</span>
34
</div>
35
<div class="px-6 py-4">
36
<div>
37
<div class="font-bold text-xl mb-2">Hello, world!</div>
38
</div>
39
<p class="text-gray-700 text-base">
40
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
41
</p>
42
</div>
43
<div class="px-6 py-4">
44
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Click me!</button>
45
<button class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded">Click me!</button>
46
</div>
47
</div>
48
```
49
50
The second one is an adapted example from the **Tailwind** CSS docs. The first one is a variant that
51
uses **BEM** instead. Both of them have *many* problems.
52
53
HTML has got over 100 elements you could use to structure your content. These examples use only
54
5: `div`, `span`, `p`, `img`, and `button`. This is not a problem in itself for small components,
55
but it can indicate one. Using `div` and `span` for everything means you're misusing HTML. This
56
is wrong: don't overlook HTML. JS or CSS may be more interesting, but the document language of
57
the WWW is HTML.
58
59
The first example uses classes in place of elements. This creates extra work for both the HTML
60
and CSS author. The CSS still mirrors the HTML structure, and the HTML is much more verbose than
61
it needs to be. The word "button" or "btn" appears 8 times for each button. Ideally, it should
62
appear two times: once in the opening tag and once in the closing tag.
63
64
The second example intentionally has the same markup tree as the first one. However, the classes
65
changed a lot. Tailwind uses classes instead of CSS rules. It leads to repetition. If you don't
66
want to repeat, you use components. But what if you don't do components? Then use `@apply` in
67
CSS. Yes, CSS. So you're basically writing CSS only with a different syntax and less flexibility.
68
69
A Simpler Way
70
-------------
71
72
Let's strip the classes and focus on the markup tree for now. The two examples are identical in
73
this regard.
74
75
```html
76
<div>
77
<div>
78
<img src="image.jpg" alt="A nice image">
79
<span>A nice image</span>
80
</div>
81
<div>
82
<div>
83
<div>Hello, world!</div>
84
</div>
85
<p>
86
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
87
</p>
88
</div>
89
<div>
90
<button>Click me!</button>
91
<button>Click me!</button>
92
</div>
93
</div>
94
```
95
96
Now you see what I said? This tree is not semantic at all. Let's find the appropriate elements for
97
each generic one.
98
99
```html
100
<article>
101
<figure>
102
<img src="image.jpg" alt="A nice image">
103
<figcaption>A nice image</figcaption>
104
</figure>
105
<section>
106
<header>
107
<h2>Hello, world!</h2>
108
</header>
109
<p>
110
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
111
</p>
112
</section>
113
<menu>
114
<button>Click me!</button>
115
<button>Click me!</button>
116
</menu>
117
</article>
118
```
119
120
In case you're not familiar with the new elements, the quick meaning is:
121
122
* [article](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article) - a self-contained
123
piece of content that makes sense independently from the rest of the page
124
* [figure](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure) - a piece of content
125
that is referenced from the main content, but can stand alone
126
* [figcaption](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption) - a caption
127
for a `figure`'s other content (optional)
128
* [section](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section) - a thematic grouping
129
of content, typically with a heading
130
* [header](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header) - header for the
131
document or a smaller part of it, can include context, navigation or information about the
132
content
133
* [h2](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2) - a second-level section
134
heading (you probably knew this one already)
135
* [menu](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu) - a list of commands
136
available to take on a specific part of the content
137
138
Please read the MDN articles I linked if you want to know more about these elements.
139
140
Depending on the other needs of your website or application, you will probably need to add a few
141
classes. However, unlike the other examples, classes should be used as little as possible. Let's
142
remember some things from the examples:
143
144
* The article is supposed to be a card and styled as such.
145
* The first button is the primary action, and the second one is the secondary action.
146
147
In this site, let's say not all articles are cards, but since this one *is* a card, we'll
148
classify it as such. Let's also say that the secondary buttons are more common, this means we'll
149
add a class to the primary button and style that later.
150
151
```html
152
<article class="card">
153
<figure>
154
<img src="image.jpg" alt="A nice image">
155
<figcaption>A nice image</figcaption>
156
</figure>
157
<section>
158
<header>
159
<h2>Hello, world!</h2>
160
</header>
161
<p>
162
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
163
</p>
164
</section>
165
<menu>
166
<button class="button-primary">Click me!</button>
167
<button>Click me!</button>
168
</menu>
169
</article>
170
```
171
172
Now, let's write a basic stylesheet for this. It won't look exactly like the second example for
173
the sake of simplicity, but it could easily be made to look like that. We're going to use a CSS
174
selector you've probably only seen in resets and to set the font on the `html` element, the
175
tag selector. We're also going to use some new CSS smarts to make the styles more maintainable.
176
177
```css
178
html, button, input, select, textarea {
179
font-family: system-ui, sans-serif;
180
}
181
article.card {
182
background-color: whitesmoke;
183
border-radius: 12px;
184
box-shadow: 0 0 4px #00000040;
185
display: flex;
186
flex-direction: column;
187
gap: 1rem;
188
overflow: hidden;
189
}
190
figure {
191
display: flex;
192
flex-direction: column;
193
gap: 0.25rem;
194
}
195
figure > img {
196
width: 100%;
197
height: auto;
198
}
199
figcaption {
200
font-style: italic;
201
opacity: 0.875;
202
}
203
article.card > section {
204
padding-left: 1rem;
205
padding-right: 1rem;
206
}
207
article.card > menu, menu.buttonbox {
208
display: flex;
209
gap: 1rem;
210
justify-content: flex-end;
211
}
212
button, .button, /* provide alternative where it makes sense, since we may want to make something else look like a button */
213
input:is([type="button"], [type="submit"], [type="reset"]) {
214
background-color: white;
215
color: orange;
216
border: 4px solid currentColor;
217
padding: 0.5rem 1rem;
218
display: inline-flex;
219
align-items: center;
220
gap: 0.5rem;
221
border: none;
222
border-radius: 4px; /* Border radii are a decoration so pixels are fine */
223
}
224
:is(button, .button, input:is([type="button"], [type="submit"], [type="reset"])).button-primary {
225
background-color: orange;
226
color: white;
227
}
228
```
229
230
Observations:
231
* We provide alternatives for some tag selectors where it makes sense, in case we want to make
232
something else look like a button. However, we don't force using both when it's already clear:
233
`<button>` will produce a styled button, same as `<a class="button">`. `<button class="button">`
234
is redundant.
235
* The `>` child selector is used to avoid leaking styles in more complex nested layouts.
236
* We use the `:is()` pseudo-class to group selectors that have the same styles. This is a new
237
feature in CSS and it saves us from writing an enormous amount of combinations.
238
239
Conclusion
240
----------
241
242
Now, writing HTML is much easier: the CSS will adapt to what you intended to describe. The CSS
243
is also much easier to maintain: the style can be changed easily without changing the HTML. The
244
elements are always styled automatically, and you can copy-paste a snippet of plain HTML
245
and have it magically match the rest of your site.
246
247
A more complete framework for this could add some layout container utilities. For example, a
248
`grid` class that makes the element a grid container and uses `--width` and `--gap` custom
249
properties to position the children. There could also be layout *elements* to use in place of
250
divs like `x-hbox` and `x-vbox` that are flex containers. This would indicate the default style,
251
and an additional class or ID would be used to make them responsive as well. Utility classes
252
aren't bad, but they should be used for the things that can't cause repetition - which side a
253
dialogue should emerge from, or whether to add padding in a generic row container.
254
255
Frameworks Using Semantic CSS
256
------------------------------
257
258
* [Pico CSS](https://picocss.com/) - does it very well, I should take some inspiration from it
259
* [Water.css](https://watercss.kognise.dev/) - a very minimalistic CSS framework, primarily intended
260
for publishing, but also includes interactive elements
261
* [MVP.css](https://andybrewer.github.io/mvp/) - a basic stylesheet for plain HTML made for any
262
site to look acceptable
263
* The roundabout also uses semantic CSS. Once the API is stabilised a little the CSS will be
264
released as a framework.
265
* You might not even need a framework.
266